package messaging;

import java.io.Serializable;
import java.util.Hashtable;
import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.MessageProducer;
import javax.jms.ObjectMessage;
import javax.jms.Session;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;

public class PublisherGateway {

	// One connection is established per client - all Gateways use the same
	// connection but different sessions
	private static Connection connection;
	protected Session session;
	protected MessageProducer messageProducer;

	// Save the name if you wish to later identify the Gateway
	private String destinationName;

	// Save the number of instances
	private volatile static int count = 0;

	/**
	 * After creation of the gateway the connection and session are already open. 
	 * Resources must be released by calling close() when the gateway is no longer needed.
	 * 
	 * @param connectionFactoryName Defines the name of the connection factory which is looked up using JNDI.           
	 * @param destinationName  Defines the name for a destination (JMS queue or Topic) which is going to be observed.         
	 * @throws NamingException Thrown when JNDI is unable to find the connectionFactory or the initial context.           
	 * @throws JMSException Thrown when the JMS provider is unavailable i.e. connection or session cannot be established or the destination does not exist.             
	 */
	public PublisherGateway(String connectionFactoryName, String destinationName)
			throws NamingException, JMSException {

		this.destinationName = destinationName;
		synchronized (PublisherGateway.class) {
			PublisherGateway.count++;
		}

		Hashtable<String, String> environment = new Hashtable<String, String>();
		environment.put(Context.INITIAL_CONTEXT_FACTORY, "org.exolab.jms.jndi.InitialContextFactory");
		environment.put(Context.PROVIDER_URL, "tcp://localhost:3035");
		environment.put(Context.SECURITY_PRINCIPAL, "admin");
		environment.put(Context.SECURITY_CREDENTIALS, "openjms");

		// Use jndi.properties resource file to get the InitialContext
		Context context = new InitialContext(environment);

		// Set up the connection
		synchronized (PublisherGateway.class) {
			if (connection == null) {
				ConnectionFactory connectionFactory = (ConnectionFactory) context.lookup(connectionFactoryName);
				connection = connectionFactory.createConnection();
				connection.start();
				//logger.debug("First connection established.");
			}
		}

		// Set up a new session using the connection
		session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);

		// Set up the message producer using a destination
		Destination destination = (Destination) context.lookup(destinationName);
		messageProducer = session.createProducer(destination);

		// Close the context
		context.close();
	}

	/**
	 * Method should be called when the gateway is no longer needed to free up resources.
	 */
	public void close() {
		try {
			messageProducer.close();
			session.close();
			synchronized (PublisherGateway.class) {
				if (--PublisherGateway.count == 0) {
					connection.stop();
					connection.close();
					connection = null;
					//logger.debug("Last instance of ObserverGateway removed and connection closed.");
				}
			}
		} catch (JMSException e) {
			//logger.error(e.getMessage() + e.getStackTrace());
		} catch (NullPointerException e) {
			//logger.error(e.getMessage() + e.getStackTrace());
		}
	}
	
	/**
	 * Method for publishing object messages.
	 * 
	 * @param payload The object that will be sent in the message. 
	 * @throws JMSException Thrown when the message cannot be created or sent due to an error with the session or connection.
	 */
	public <E extends Serializable> void publish(E payload) throws JMSException {
		ObjectMessage message = session.createObjectMessage(payload);
		System.out.println("published to destination " + destinationName);
		messageProducer.send(message);
	}

	/**
	 * @return The name of a destination associated with the gateway.
	 */
	public String getDestinationName() {
		return destinationName;
	}

}
